<!-- 甘特图 -->
<template>
	<div ref="ganttContainer" class="xdc-gantt" />
</template>

<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref, watch, computed } from 'vue';
import { ElNotification, NotificationHandle } from 'element-plus';
import { gantt, Task, Link, ZoomLevels } from 'dhtmlx-gantt';
const props = defineProps({
	// 操作类型（'view'/'edit'）
	type: {
		type: String,
		default() {
			return 'view';
		},
	},
	// 表格标题
	gridTitle: String,
	// 任务列表
	tasks: {
		type: Array<Task>,
	},
	// 连线列表
	links: {
		type: Array<Link>,
	},
	durationUnit: {
		type: String,
		default: 'day',
	},
	durationStep: {
		type: Number,
		default: 1,
	},
	// 表格列
	columns: {
		type: Array,
	},
	// 悬浮框
	tooltip: {
		type: Boolean,
		default: true,
	},
	// 悬浮框文本
	tooltip_text: {
		type: Function as unknown as (start: Date, end: Date, task: Task) => string,
		default(start: Date, end: Date, task: Task): string {
			return `
				<b>${task.name}</b>
				完成比例：${Math.round(task.progress * 100)}%
			`;
		},
	},
	// 任务文本
	task_text: {
		type: Function as unknown as (start: Date, end: Date, task: Task) => string,
		default(start: Date, end: Date, task: Task): string {
			return `
				<b>${task.name}</b>
				<i>完成比例：${Math.round(task.progress * 100)}%</i>
			`;
		},
	},
	// 弹框校验
	onLightboxSave: {
		type: Function,
		default() {
			return true;
		},
	},
	onAfterTaskAdd: Function,
	onAfterTaskUpdate: Function,
	onAfterTaskDelete: Function,
	onAfterTaskDrag: Function,
	onAfterTaskMove: Function,
});
const gridWidth = computed(() => {
	return props.columns.reduce((prev, current) => prev + current.width, 0);
});
const ganttContainer = ref<HTMLElement>();
if (!import.meta.env.PROD) {
	(window as any).gantt = gantt;
}
let zoomNotification: NotificationHandle;
onMounted(() => {
	render();
	zoomNotification = ElNotification({
		type: 'info',
		title: '提示',
		position: 'bottom-right',
		dangerouslyUseHTMLString: true,
		message: '使用 <b>ctrl键 + 鼠标滚轮</b> 缩放甘特图',
		duration: 1000 * 10,
	});
});

watch(
	() => props.tasks,
	(val, oldVal) => {
		nextTick(() => {
			render();
		});
	}
);
onBeforeUnmount(() => {
	unobserve();
	detachEvents();
	gantt.clearAll();
	zoomNotification?.close();
});
let ganttReady = false;
/**
 * 是否只读
 */
const readonly = computed(() => props.type !== 'edit');
/**
 * 渲染甘特图
 */
function render() {
	console.log('[甘特图] render方法');
	ganttReady = false;
	unobserve();
	gantt.clearAll();
	initConfig();
	gantt.init(ganttContainer.value);
	gantt.parse({ data: props.tasks });
	ganttReady = true;
	observe();
}
/**
 * 初始化甘特图配置
 */
function initConfig() {
	console.log('[甘特图] initConfig方法');
	// 1 功能配置
	gantt.config.readonly = readonly.value;
	gantt.config.duration_unit = props.durationUnit;
	gantt.config.duration_step = props.durationStep;
	// 插件
	gantt.plugins({
		tooltip: props.tooltip, // 鼠标悬浮框
		marker: true,
	});

	// 1.3 设置中文
	gantt.i18n.setLocale('cn');
	gantt.templates.tooltip_text = props.tooltip_text;

	// 1.5 初始化的时候就展开树结构
	gantt.config.open_tree_initially = true;
	// 1.6 显示到任务上的文本
	gantt.templates.task_text = props.task_text;
	// 1.8 今天
	const today = new Date();
	gantt.addMarker({
		start_date: today,
		css: 'today',
		text: '今天',
		title: 'Today: ' + gantt.date.date_to_str('%m月%d日')(today),
	});

	// 2 时间配置
	gantt.config.date_format = '%Y-%m-%d %H:%i:%s';
	// 2.1 时间坐标轴单位 minute/hour/day/week/quarter/month/year
	// gantt.config.scale_unit = "hour";
	// 2.2 日期格式
	// gantt.config.date_scale = "%H";
	// 2.3 时间坐标为月份的时候  先显示年份再月份
	// gantt.config.subscales = [{unit: "month", step: 1, date: "%Y,%F"}];

	var weekScaleTemplate = function (date) {
		var dateToStr = gantt.date.date_to_str('%M %d');
		var endDate = gantt.date.add(gantt.date.add(date, 1, 'week'), -1, 'day');
		return dateToStr(date) + ' - ' + dateToStr(endDate);
	};

	var dayTemplate = function (date: Date) {
		var dateToStr = gantt.date.date_to_str('%m月%d日');
		var weekDay = gantt.date.date_to_str('%D');
		return dateToStr(date) + ' 星期' + weekDay(date);
	};

	var weekdayStyle = function (date: Date) {
		// you can use gantt.isWorkTime(date)
		// when gantt.config.work_time config is enabled
		// In this sample it's not so we just check week days

		if (date.getDay() === 0 || date.getDay() === 6) {
			return 'weekend';
		}
		return '';
	};

	// gantt.config.scales = [
	// 	{ unit: 'day', step: 1, format: '%m月%d日 星期%D', css: daysStyle },
	// 	{ unit: 'hour', step: 1, format: '%H:%i' }, //这里的step要根据当前的班次设置来。如果是三班倒则是8，如果是两班倒则是12。
	// ];

	// 2.4 定义从后端获取或发送到后端的日期数据解析格式
	// gantt.config.xml_date = '%Y-%m-%d %H:%i:%s';

	// 3 布局配置
	gantt.config.columns = props.columns;
	gantt.config.reorder_grid_columns = true;
	gantt.config.keep_grid_width = false;
	gantt.config.grid_resize = true;
	const titles = getGanttTitles();
	gantt.config.layout = {
		css: 'gantt_container',
		cols: [
			{
				width: gridWidth.value,
				rows: [
					{ html: titles.grid, height: 50 },
					{ view: 'grid', scrollX: 'gridScroll', scrollable: true, scrollY: 'scrollVer' },
					{ view: 'scrollbar', id: 'gridScroll', group: 'horizontal' },
				],
			},
			{ resizer: true, width: 1 },
			{
				rows: [
					{
						html: titles.timeline,
						height: 50,
					},
					{ view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' },
					{ view: 'scrollbar', id: 'scrollHor', group: 'horizontal' },
				],
			},
			{ view: 'scrollbar', id: 'scrollVer' },
		],
	};
	// gantt.config.grid_width = 320;
	gantt.config.resize_rows = true;
	// 显示单元格
	gantt.config.show_task_cells = true;
	gantt.config.scale_height = 50;
	gantt.templates.timeline_cell_class = function (item, date) {
		if (date.getDay() == 0 || date.getDay() == 6) {
			return 'weekend';
		}
	};

	// 4 拖动配置
	// 3.1 自动调整类型,当存在子节点时不自动升级为project
	gantt.config.auto_types = false;
	// 3.2 设置能否拖动进度
	gantt.config.drag_progress = false;
	// 3.3 设置能否拖动任务
	gantt.config.drag_move = !readonly.value;
	// 3.4 设置能否拖动连线
	gantt.config.drag_links = false;
	// 3.5 设置能否调整任务大小
	gantt.config.drag_resize = !readonly.value;
	// 3.6 单击显示添加详情
	gantt.config.details_on_create = !readonly.value;
	// 3.7 双击显示明细
	gantt.config.details_on_dblclick = true;
	//时间范围自动适应
	gantt.config.fit_tasks = true;

	// 5 缩放配置
	var zoomConfig = {
		minColumnWidth: 100,
		maxColumnWidth: 150,
		levels: [
			[{ unit: 'year', step: 1, format: '%Y年' }],
			[
				{
					unit: 'quarter',
					step: 1,
					format: function (date: Date) {
						const dateToStr = gantt.date.date_to_str('%Y年');
						const txts = ['一', '二', '三', '四'];
						return `${dateToStr(date)} ${txts[(date.getMonth() + 1) % 3]}季度`;
					},
				},
				{ unit: 'month', step: 1, format: '%m月' },
			],
			[
				{ unit: 'month', format: '%Y年%m月', step: 1 },
				{
					unit: 'week',
					step: 1,
					format: function (date: Date) {
						var dateToStr = gantt.date.date_to_str('%m.%d');
						var startDate = gantt.date.add(date, -6, 'day');
						var weekNum = gantt.date.date_to_str('%W')(date);
						return '第' + weekNum + '周, ' + dateToStr(startDate) + ' - ' + dateToStr(date);
					},
				},
			],
			[
				{ unit: 'month', format: '%Y年%m月', step: 1 },
				{ unit: 'day', format: '%m月%d日', step: 1, css: weekdayStyle },
			],
			[
				{ unit: 'day', format: '%Y年%m月%d日 星期%D', step: 1, css: weekdayStyle },
				{ unit: 'hour', format: '%H:%i', step: 1 },
			],
		],
		activeLevelIndex: 3,
		// startDate: new Date(2023, 02, 27),
		// endDate: new Date(2023, 03, 20),
		useKey: 'ctrlKey',
		trigger: 'wheel',
		element: function () {
			return gantt.$root.querySelector('.gantt_task');
		},
	};
	gantt.ext.zoom.init(zoomConfig);

	// 6 绑定事件
	attachEvents();
}
function attachEvents() {
	gantt.attachEvent('onAfterTaskAdd', function (id, item) {
		console.log('onAfterTaskAdd');
		props.onAfterTaskAdd?.apply(null, arguments);
	});
	gantt.attachEvent('onAfterTaskUpdate', function (id, item) {
		console.log('onAfterTaskUpdate');
		props.onAfterTaskUpdate?.apply(null, arguments);
	});
	gantt.attachEvent('onLightboxSave', function (id, task, is_new) {
		console.log('onLightboxSave');
		return props.onLightboxSave.apply(null, arguments);
	});
	gantt.attachEvent('onAfterTaskDelete', function (id, item) {
		console.log('onAfterTaskDelete');
		props.onAfterTaskDelete?.apply(null, arguments);
	});
	gantt.attachEvent('onAfterTaskDrag', function (id, mode, e) {
		console.log('onAfterTaskDrag');
		props.onAfterTaskDrag?.apply(null, arguments);
	});
	gantt.attachEvent('onAfterTaskMove', function (id, parent, tindex) {
		console.log('onAfterTaskMove');
		props.onAfterTaskMove?.apply(null, arguments);
	});
	gantt.attachEvent('onGanttScroll', function (left, top) {
		// any custom logic here
		if (!ganttReady) {
			return;
		}
		console.log('onGanttScroll', left, top);
		const chart: HTMLElement = ganttContainer.value.querySelector('.gantt_task');
		const timelineTitleWrapper: HTMLElement = ganttContainer.value.querySelector(
			'.xdc-gantt-timeline-title-wrapper'
		);
		timelineTitleWrapper.style.left = -1 * chart.scrollLeft + 'px';
	});
}
function detachEvents() {
	gantt.detachAllEvents();
}
let mutationObserver, resizeObserver;
function observe() {
	const taskScale = ganttContainer.value.querySelector('.gantt_task_scale');
	mutationObserver = new MutationObserver((mutationsList, observer) => {
		console.log('[甘特图] MutationObserver', mutationsList);
		adjustTitle();
	});
	mutationObserver.observe(taskScale, { attributes: false, childList: true, subtree: true });
	resizeObserver = new ResizeObserver(entries => {
		console.log('[甘特图] ResizeObserver', entries);
		adjustTitle();
	});
	resizeObserver.observe(taskScale);
}
function unobserve() {
	mutationObserver?.disconnect();
	mutationObserver = null;
	resizeObserver?.disconnect();
	resizeObserver = null;
}
function adjustTitle() {
	console.log('[甘特图] adjustTitle');
	if (!ganttReady) {
		return;
	}
	const chart: HTMLElement = ganttContainer.value.querySelector('.gantt_task');
	const taskScale: HTMLElement = ganttContainer.value.querySelector('.gantt_task_scale');
	const taskScaleRect = taskScale.getBoundingClientRect();
	const taskBarRects = Array.from(
		ganttContainer.value.querySelectorAll(
			getMainTasks()
				.map(task => `.gantt_bar_task[task_id="${task.id}"]`)
				.join(',')
		)
	).map(bar => bar.getBoundingClientRect());
	const timelineTitleWrapper: HTMLElement = ganttContainer.value.querySelector(
		'.xdc-gantt-timeline-title-wrapper'
	);
	timelineTitleWrapper.style.width = taskScale.getBoundingClientRect().width + 'px';
	timelineTitleWrapper.style.left = -1 * chart.scrollLeft + 'px';
	const timelineTitleCells = timelineTitleWrapper.querySelectorAll(
		'.xdc-gantt-timeline-title-cell'
	);
	timelineTitleCells.forEach((cell: HTMLElement, index) => {
		if (!taskBarRects[index]) {
			return;
		}
		cell.style.width = taskBarRects[index].width + 'px';
		cell.style.left = taskBarRects[index].left - taskScaleRect.left + 'px';
	});
}
function getMainTasks() {
	return props.tasks?.filter(task => !task.parent);
}
function getGanttTitles() {
	return {
		grid: `<div class="xdc-gantt-grid-title xdc-text-overflow" title="${props.gridTitle}">${props.gridTitle}</div>`,
		timeline: `<div class="xdc-gantt-timeline-title">
									<div class="xdc-gantt-timeline-title-wrapper" style="">
										${getMainTasks()
											?.map(task => {
												return `<span class="xdc-gantt-timeline-title-cell xdc-text-overflow" style="" main_task_id="${task.id}" title="${task.name}">
													${task.name}
												</span>`;
											})
											.join('')}
									</div>
			        </div>`,
	};
}
defineExpose({ render });
</script>
<style lang="scss">
@import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
.xdc-gantt {
	height: 100%;
	// 标题
	.xdc-gantt-grid-title {
		height: 100%;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 18px;
	}
	.xdc-gantt-timeline-title {
		position: relative;
		height: 100%;
		overflow: hidden;
	}
	.xdc-gantt-timeline-title-wrapper {
		position: absolute;
		left: 0;
		top: 0;
		height: 100%;
	}
	.xdc-gantt-timeline-title-cell {
		position: absolute;
		top: 0;
		height: 100%;
		display: flex;
		justify-content: center;
		align-items: center;
		height: 100%;
		border-left: 1px solid #ebebeb;
		border-right: 1px solid #ebebeb;
		font-size: 18px;
	}
	// 表格头字体
	.gantt_grid_scale .gantt_grid_head_cell,
	.gantt_task .gantt_task_scale .gantt_scale_cell {
		color: var(--xdc-gantt-th-color);
	}
	.weekend {
		background-color: #f4f7f4;
	}
}
</style>
